
#include "fxcc/core/graph/opengl3/ImGuiGlfwApp.h"
#include "fxcc/core/graph/common/Geometry.h"
#include "fxcc/imgui/Editor.h"

#include "fxcc/core/graph/common/ImageMipmap.h"

#include "fxcc/core/graph/opengl3/Texture2DJack.h"
#include "fxcc/core/graph/opengl3/Texture2DArrayJack.h"
#include "fxcc/core/graph/opengl3/Texture3DJack.h"
#include "fxcc/core/graph/opengl3/TextureCubeArrayJack.h"
#include "fxcc/core/graph/opengl3/MeshJack.h"
#include "fxcc/core/graph/opengl3/IndexBuffer.h"
#include "fxcc/core/graph/opengl3/RenderTargetJack.h"

#include "fxcc/core/AssertManager.h"

#include "fxcc/bin/Serializer.h"

#include <string>
#include <codecvt>
#include <locale>

using namespace fxcc::platform;
using namespace fxcc::graph::common;
using namespace fxcc::graph::opengl3;
using namespace fxcc::imgui::graph::opengl3;
using BaseApp = fxcc::imgui::graph::opengl3::GlfwApp;

#undef main

extern "C" {
    __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
}

struct SDFResource :public fxcc::Base
{
    std::string m_DirPath;

    SDFInfo m_SDFInfo;

    SDFCharMap m_SDFCharMap;

    std::vector<std::shared_ptr<fxcc::graph::common::Texture2DJack>> m_SDFTextures;

    std::shared_ptr<fxcc::graph::common::MeshJack> m_SDFMeshJack;
    
    SDFResource(const std::string& dir) :m_DirPath(dir)
    {
        m_Available = Init();
    };

    bool Init()
    {
        std::string m_SDFInfoPath = (fs::path(m_DirPath) / MSDFConfigName()).string();
        std::string m_SDFCharMapPath = (fs::path(m_DirPath) / CharMapFileName()).string();

        fxcc::yml::Serializer ymlser;
        fxcc::bin::Serializer binser;

        ymlser.DecodePath<SDFInfo>(m_SDFInfoPath, m_SDFInfo);
        binser.ReadPath<SDFCharMap>(m_SDFCharMapPath, m_SDFCharMap);

        std::string bmpSuffix = SDFInfo::SDFPageSuffix();
        for (int pageIndex = 0; pageIndex < m_SDFInfo.m_NumPages; pageIndex++)
        {
            const std::string bmpFilePath = (fs::path(m_DirPath) / (std::to_string(pageIndex))).string() + bmpSuffix;

            fxcc::graph::common::ImageMipmap::Desc desc;
            desc.m_WICFlipY = false;
            desc.m_wicHdr = false;
            desc.m_wicReq = 3;
            desc.m_wicPath = bmpFilePath;

            auto texture = fxcc::graph::common::GraphImpl::CreateTexture2D(desc);
            m_SDFTextures.push_back(texture);
        }

        return true;
    };

    std::string m_Text;

    static std::string MSDFConfigName()
    {
        return "msdf.config";
    }
    static std::string CharMapFileName()
    {
        return "char.map";
    }
};

struct SDFEffect :public fxcc::graph::opengl3::Effect3d
{
    SDFEffect() :fxcc::graph::opengl3::Effect3d(FragCode())
    {
        m_PipeLineType.m_RasterizerType = fxcc::graph::common::RasterizerType::_NONE_;

        m_Shader->Use();
        m_Shader->setInt("msdfMap", 0);

    }

    void InstSDFChar(const fxcc::graph::common::Texture2DJack* texture)
    {
        texture->SetShader(0);
        auto& buffers = fxcc::graph::opengl3::IBuffers::Inst();
        
        buffers.m_QuadMesh->Bind();
        buffers.m_QuadMesh->DrawElementsAuto();

    }
    static std::string FragCode()
    {
        return R"(
out vec4 FragColor;

uniform sampler2D msdfMap;
uniform vec4 bgColor;
uniform vec4 fgColor;
uniform float pxRange; // set to distance field's pixel range

float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
}

float screenPxRange() {
    vec2 unitRange = vec2(pxRange)/vec2(textureSize(msdfMap, 0));
    vec2 screenTexSize = vec2(1.0)/fwidth(TexCoord);
    return max(0.5*dot(unitRange, screenTexSize), 1.0);
}
void main()
{
    vec3 msd = texture(msdfMap, TexCoord).rgb;
    float sd=median(msd.r ,msd.g, msd.b);
    float screenPxDistance =screenPxRange()* (sd-0.5f);
    float opacity=clamp(screenPxDistance+0.5f,0.0f,1.0f);
    FragColor=mix(bgColor, fgColor,opacity);
}


)";
    }
};
struct SimpleApp : public BaseApp
{
public:

    fxcc::graph::common::MaterialView m_MaterialView;

    std::shared_ptr<ImageMipmap> m_ImageMipMap;

    std::shared_ptr< SDFEffect> m_SDFEffect;

    std::shared_ptr< SDFResource> m_SDFResource;

    glm::vec4 color;

    fxcc::graph::common::Camera camera;

    float pxRange{ 4.0f };

    std::string m_Text;

    SimpleApp(const fxcc::platform::common::App::Desc& desc) : BaseApp(desc), color(1.0)
    {
    }
    bool Init() override
    {
        if (!BaseApp::Init())
        {
            return false;
        }

        if (!InitResources())
        {
            return false;
        }

        return true;
    };


    bool InitResources()
    {
        ImageMipmap::MipMapData image(1024, 1024, 4, 2);
        fxcc::graph::opengl3::RenderTargetJack::Desc desc(1, image);


        camera.SetPosition(glm::vec3(5.0f));
        camera.LookAt(glm::vec3(0.0f));

        auto& am = fxcc::AssertManager::Inst();

        fs::path _p1("sdf_out2/simkai");
        m_SDFResource = std::make_shared<SDFResource>(_p1.string());
 
        {
            const std::string fragCode = R"(


)";
            m_SDFEffect = std::make_shared<SDFEffect>();
            m_SDFEffect->Influence(m_MaterialView);

            m_MaterialView.m_Params.SetFloat("pxRange", 4.0f);
            m_MaterialView.m_Params.SetVec4("bgColor", glm::vec4(1.0f));
            m_MaterialView.m_Params.SetVec4("fgColor", glm::vec4(0.0f));
            m_MaterialView.m_Textures.SetTexture2D("msdfMap", "BIZ-UDMinchoM/33.png");
        
        }
        return true;
    };

    fxcc::imgui::Editor editor;

    static std::wstring UTF8ToWString(const std::string& utf8Str)
    {
        std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
        return converter.from_bytes(utf8Str);
    }
    void OnEdit() override
    {
        ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());

        ImGui::Begin("Material");
        editor.EditInst<fxcc::graph::common::MaterialView>("font Mat", m_MaterialView);
        ImGui::End();


        ImGui::Begin("SDFs");
        editor.EditInst<std::string>("text", m_Text);
        std::wstring wtext = UTF8ToWString(m_Text);
        for (const wchar_t& ch : wtext)
        {
            int charcode = static_cast<int>(ch);
            const auto& sdfchar = m_SDFResource->m_SDFCharMap.m_SDFChars[charcode];
            
            ImGui::Text("size %d %d bearing %d %d advance %d %d Grid (%d %d)/ (%d %d)", sdfchar.m_Size.x, sdfchar.m_Size.y, sdfchar.m_Bearing.x, sdfchar.m_Bearing.y, sdfchar.m_Advance.x, sdfchar.m_Advance.y, 
                sdfchar.m_TexGridIndex.x,
                sdfchar.m_TexGridIndex.y,
                sdfchar.m_TexGridNum.x,
                sdfchar.m_TexGridNum.y
            );
            ImGui::SameLine();

            int pageIndex = sdfchar.m_PageIndex;

            ImVec2 texStart;
            ImVec2 texEnd;
            texStart.x = (float)sdfchar.m_TexGridIndex.x / (float)sdfchar.m_TexGridNum.x;
            texStart.y = (float)sdfchar.m_TexGridIndex.y / (float)sdfchar.m_TexGridNum.y;
            texEnd.x = texStart.x + 1.0f / (float)sdfchar.m_TexGridNum.x;
            texEnd.y = texStart.y + 1.0f / (float)sdfchar.m_TexGridNum.y;

            ImGui::Image(m_SDFResource->m_SDFTextures[pageIndex]->GetTextureId(), { 50,50 },texStart, texEnd);
        }

        for (const auto& texture : m_SDFResource->m_SDFTextures)
        {
            ImGui::Image(texture->GetTextureId(), { 200,200 });
        }
        ImGui::End();

        ImGui::Begin("Framebuffer");
        editor.AddFrameBuffer(m_RTJack->m_Textures[0]->GetTextureId());
        ImGui::End();
    }
    fxcc::graph::common::Transform transform;
    std::shared_ptr<fxcc::graph::opengl3::RenderTargetJack> m_RTJack;

    void OnDestroy() override
    {

        BaseApp::OnDestroy();
    }
    void OnFrameRender() override
    {
        glViewport(0, 0, m_Desc.m_Size.x, m_Desc.m_Size.y);

        glClearColor(color.x, color.y, color.z, color.w);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        m_RTJack->Begin();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearColor(color.x, color.y, color.z, color.w);

        //m_Effect->Begin();
        m_SDFEffect->SetFixed();

        fxcc::graph::common::Effect3d::PassData passData;
        fxcc::graph::common::Effect3d::ObjData objData;
        m_SDFEffect->GetTexture2DJackBufferData(64, 64, passData, objData);
        m_SDFEffect->SetPassData(passData);
        m_SDFEffect->SetObjData(objData);
        m_SDFEffect->SetFixed();
        //m_SDFEffect->m_Shader->setInt("msdfMap", 0);
        //m_SDFEffect->m_Shader->setInt("fgIndex", fgIndex);
        //m_SDFEffect->m_Shader->setFloat("pxRange", pxRange);
        //m_SDFEffect->m_Shader->setVec4("bgColor", { 1.0f,1.0f,1.0f,1.0f });
        //m_SDFEffect->m_Shader->setVec4("fgColor", { 0.0f,0.0f,0.0f,1.0f });
        m_SDFEffect->UseMaterial(m_MaterialView);
        //m_SDFEffect->InstSDFChar(m_Texture.get());
        m_SDFEffect->End();
        m_RTJack->End();
    };

    void OnFramebuffersize(int w, int h) override
    {
        BaseApp::OnFramebuffersize(w, h);
        camera.SetAspect(m_Desc.Aspect());
        fxcc::graph::opengl3::RenderTargetJack::Desc desc(1, { w, h, 3, 0 });
        m_RTJack = std::make_shared<fxcc::graph::opengl3::RenderTargetJack>(desc);


    }
    void OnUpdate() override
    {
        BaseApp::OnUpdate();

        float ctTime = m_GlfwlTimer.m_CurrentTime;
        float deltaTime = m_GlfwlTimer.m_DeltaTime;


    }
};

int main()
{
    ztclog::pushFunction([](const ztclog::Line& line)
        {
            if (line.m_Type != ztclog::Line::Type::_line_debug_)
            {
                std::cout << line.m_Text << std::endl;
            } });

            using _App = SimpleApp;
            using _Desc = fxcc::platform::common::App::Desc;

            _Desc desc;
            desc.m_Pos = glm::ivec2(100, 100);
            desc.m_Size = glm::ivec2(900, 600);
            desc.m_ScaleFactor = 1.0f;

            desc.m_Title = "sdh render ";
            std::shared_ptr<_App> _app = std::make_shared<_App>(desc);
            return _app->Run();
};
